/*
 * app_cli_command.c
 *
 *  Created on: 2023年6月29日
 *      Author: HXF
 */
#include "app_cli_command.h"
#include "app_cli_arguments.h"

void DS_cli_pre_cmd_hook(ds_cli_command_arg_t* arguments)
{
  (void) arguments;
}

/***************************************************************************//**
 * @brief
 *   Hook executed after the command. Unless specifically redefined to
 *   something, this hook does nothing.
 *
 * @param[in] arguments     The arguments passed to the command handler.
 *
 ******************************************************************************/
void DS_cli_post_cmd_hook(ds_cli_command_arg_t* arguments)
{
  (void) arguments;
}

static bool escape(char input[],int i,int size)
{
  // Check if next character is one of the escaped ones
  if(input[i + 1] == '"' || input[i + 1] == '{' || input[i + 1] == '\\'){
      // Next input matches, so shift array
      memmove(input + i, input + i + 1, size - i);
      return true;
  }else{
      // Unknown sequence
      return false;
  }
}

int DS_cli_tokenize(char input[], int len, int *token_c,char *token_v[])
{
  // Get size of string and declare variables
  uint32_t input_size = len;
  uint32_t i = 0;
  // Ignore preceding space
  while(input[i] == ' ' || input[i] == '\r' || input[i] == '\n'){
      i++;
  }
  token_v[0] = input + i;
  *token_c = 1;
  while(i < input_size){
      if(input[i] == '{' || input[i] == '"') {
          // Start of segment where splitting should not be performed
          char end_char;
          if(input[i] == '{'){
              end_char = '}';
          }else{
              end_char = '"';
              // Token ignores " character
              token_v[*token_c - 1] = input + i + 1;
          }
          // Verify that preceding character is ','/*space*/ or end of string
          if (i > 1 && (input[i - 1] != ',' && input[i - 1] != '\0')) {
              return DS_STATUS_INVALID_PARAMETER;
          }
          // Loop through input to find end character
          i++;
          while(input[i] != end_char){
              if(input[i] == '\\'){
                  // Next character should be escaped
                  if(!escape(input, i, input_size)){
                      // Escape error
                      return DS_STATUS_INVALID_PARAMETER;
                  }
                  input_size--;
              }
              i++;
              if(i >= input_size){
                  // End character not found, return error
                  return DS_STATUS_INVALID_PARAMETER;
              }
          }
          // Verify that next character is space or end of string
          if(input[i + 1] != ' ' && input[i + 1] != '\0'){
              return DS_STATUS_INVALID_PARAMETER;
          }
          // Remove " from string end
          if(end_char == '"'){
              input[i] = '\0';
          }
      }else if(input[i] == '\\'){
          if(!escape(input, i, input_size)) {
              // Escape error
              return DS_STATUS_INVALID_PARAMETER;
          }
          input_size--;
      }else if(input[i] == ','){
          // Turn ','/*space*/ into '\0' to indicate end of string
          input[i] = '\0';
          //Add token if next is character is meaningful
          if(i + 1 < input_size && input[i + 1] != ' '){
              // Only add argument if number of max arguments is not exceeded
              if(*token_c < DS_CLI_MAX_INPUT_ARGUMENTS){
                  //Set next token to start at the (i+1)th element in input
                  token_v[*token_c] = input + i + 1;
              }else{
                  // There are too many arguments, error needs to be signaled
                  return DS_STATUS_HAS_OVERFLOWED;
              }
              (*token_c)++;
          }
      }
      i++;
  }
  return DS_STATUS_OK;
}

static const ds_cli_command_entry_t *scan_entry(const ds_cli_command_entry_t *cmd_entry_in,
                                                bool *found,
                                                int *token_c,
                                                char *token_v[],
                                                int *arg_ofs,
                                                bool *single_flag)
{

  const ds_cli_command_entry_t *cmd_entry = cmd_entry_in;
  while((cmd_entry->name != NULL) && (*arg_ofs < *token_c)){
      if(strcmp(cmd_entry->name, token_v[*arg_ofs]) == 0){
          // Command or group found
          (*arg_ofs)++;
          // Command found, stop search
          *single_flag = true;
          *found = true;
          break;
      }
      cmd_entry++;
  }

  if(!(*found)){
      cmd_entry = NULL;
  }
  return cmd_entry;
}

const ds_cli_command_entry_t *ds_cli_command_find(ds_cli_handle_t handle,
                                                  int *token_c,
                                                  char *token_v[],
                                                  int *arg_ofs,
                                                  bool *single_flag)
{
  const ds_cli_command_entry_t *cmd_entry = NULL;
  bool found = false;

  *arg_ofs = 0;
  *single_flag = false;

  cmd_entry = handle->command_table;

  cmd_entry = scan_entry(cmd_entry, &found, token_c, token_v, arg_ofs, single_flag);
  return cmd_entry;
}

int DS_cli_command_execute(ds_cli_handle_t handle)
{
  char *token_v[DS_CLI_MAX_INPUT_ARGUMENTS];
  int token_c;
  uint32_t memory_array[DS_CLI_MAX_INPUT_ARGUMENTS];
  void *argv[DS_CLI_MAX_INPUT_ARGUMENTS];
  ds_cli_command_arg_t arguments;
  int arg_ofs;

  // Split input string
  int status = DS_cli_tokenize(handle->input_buffer, handle->input_size, &token_c, token_v);
  if(status != DS_STATUS_OK){
      return status;
  }

  // Find command
  bool single_flag;
  const ds_cli_command_entry_t *cmd_table = ds_cli_command_find(handle,
                                                                &token_c,
                                                                token_v,
                                                                &arg_ofs,
                                                                &single_flag);
  if(cmd_table == NULL){
        return DS_STATUS_NOT_FOUND;
  }

  // Convert arguments to correct type
  const ds_cli_command_info_t *cmd_info = cmd_table->command;
  status = DS_cli_arguments_convert_multiple(cmd_info->arg_type_list,
                                              token_c,
                                              token_v,
                                              arg_ofs,
                                              argv,
                                              memory_array);
  if (status != DS_STATUS_OK) {
    return status;
  }

  // Call function
  arguments.handle = handle;
  arguments.argc = token_c;
  arguments.argv = argv;
  arguments.arg_ofs = arg_ofs;
  arguments.arg_type_list = cmd_info->arg_type_list;

  DS_cli_pre_cmd_hook(&arguments);
  cmd_info->function(&arguments);
  DS_cli_post_cmd_hook(&arguments);
  // Command executed, return status that in this case is success
  return status;
}
